/*
 * Copyright (c) 2010-2011 Josef Hardi <josef.hardi@gmail.com>
 * Copyright (c) 2010 Piotr Zagawa <piotr.zagawa@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package id.duapuluhdua.dakidaki.map;

import java.util.ArrayList;
import java.util.Observable;

import android.graphics.Canvas;
import android.view.SurfaceView;

/**
 * Define the screen display.
 */
public class Screen extends Observable
{
  /** Constants */
  private final static double RANGE_100M = 100.0f;
  private final static double RANGE_200M = 200.0f;
  private final static double RANGE_INV_100M = 0.01f;

  /** Fields */
  private Integer mCurrentViewportMode = Viewport.MODE_FREE;
  private Viewport mViewport;

  private int mWidth; // default 0
  private int mHeight; // default 0

  private double mPixelSize;
  private double mPixelViewportFactor;

  private GeoPoint mPointGridStart = new GeoPoint(0,0);

  /**
   * The default constructor.
   */
  public Screen()
  {
    mViewport = new Viewport();
  }

  /**
   * Set the size of the screen. Typically the sizes are retrieved from the
   * {@link Canvas} object that comes from the {@link SurfaceView} class.
   *
   * @param width the width size.
   * @param height the height size.
   */
  public void setSize(int width, int height)
  {
    mWidth = width;
    mHeight = height;

    mViewport.setScreenAspectRatio(getAspectRatio());

    updatePixelSize();
  }

  /**
   * Get the ratio between width and height.
   *
   * @return the aspect ratio.
   */
  public double getAspectRatio()
  {
    if (getHeight() == 0) {
      return 0;
    }
    return ((double)getWidth() / (double)getHeight());
  }

  /**
   * Get the screen width.
   *
   * @return width size.
   */
  public int getWidth()
  {
    return mWidth;
  }

  /**
   * Get the screen height.
   *
   * @return height size.
   */
  public int getHeight()
  {
    return mHeight;
  }

  /**
   * Get the pixel size.
   *
   * @return pixel size.
   */
  public double getPixelSize()
  {
    return mPixelSize;
  }

  /**
   * Get the viewport width.
   *
   * @return width size.
   */
  public double getViewportWidth()
  {
    return mViewport.getWidth();
  }

  /**
   * Get the viewport height.
   *
   * @return height size.
   */
  public double getViewportHeight()
  {
    return mViewport.getHeight();
  }

  /**
   * Get the viewport left-most edge.
   *
   * @return the minimum x-axis.
   */
  public double getViewportLeft()
  {
    return mViewport.getMinX();
  }

  /**
   * Get the viewport right-most edge.
   *
   * @return the maxiumum x-axis.
   */
  public double getViewportRight()
  {
    return mViewport.getMaxX();
  }

  /**
   * Get the viewport top-most edge.
   *
   * @return the minimum y-axis.
   */
  public double getViewportTop()
  {
    return mViewport.getMinY();
  }

  /**
   * Get the viewport bottom-most edge.
   *
   * @return the maximum y-axis.
   */
  public double getViewportBottom()
  {
    return mViewport.getMaxY();
  }

  /**
   * Move the viewport region to the distance {@code fShiftX} in the horizontal
   * axis and to the distance {@code fShiftY} in the vertical axis.
   *
   * @param shiftX the distance in x-axis (m).
   * @param shiftY the distance in y-axis (m).
   */
  public void shiftViewport(float shiftX, float shiftY)
  {
    synchronized(mViewport)
    {
      mViewport.shift(shiftX * mPixelSize, shiftY * mPixelSize);
    }
    updateCurrentViewportMode();
  }

  /**
   * Present the viewport to wider or narrower region. By adding more
   * scale meters to the initial viewport region, it creates a new and wider
   * viewport region and, thus, gives a zoom-out effect. The scale meters can
   * have a negative sign in which it reduces the initial viewport region,
   * making the region narrower and, thus, gives a zoom-in effect.
   *
   * @param scaleMeters the scale meters (m).
   */
  public void zoomViewport(float scaleMeters)
  {
    boolean bChanged = false;
    synchronized(mViewport)
    {
      bChanged = mViewport.addMargin(scaleMeters, scaleMeters);
    }

    if (bChanged) {
      updateCurrentViewportMode();
    }
  }

  /**
   * Reset the region of the viewport so that the geo-point is in the center
   * of the screen. It is possible to reset the viewport mode as well.
   *
   * @param point a single geo-point.
   * @param viewportMode new viewport mode.
   */
  public void resetViewport(final GeoPoint point, Integer viewportMode)
  {
    mViewport.reset(point);
    updatePixelSize();
    setViewportMode(viewportMode);
  }

  /**
   * Reset the region of the viewport so that the collection of geo-points
   * is visible entirely in the screen.
   *
   * @param points all geo-points.
   * @param viewportMode new viewport mode.
   */
  public void resetViewport(final ArrayList<GeoPoint> points, Integer viewportMode)
  {
    mViewport.reset(points);
    updatePixelSize();
    setViewportMode(viewportMode);
  }

  public void setStartupViewportMode(Integer viewportMode)
  {
    synchronized(mCurrentViewportMode)
    {
      mCurrentViewportMode = viewportMode;
    }
  }

  /**
   * Get the current viewport mode.
   *
   * @return
   */
  public Integer getCurrentViewportMode()
  {
    Integer mode = Viewport.MODE_FREE;

    synchronized(mCurrentViewportMode)
    {
      mode = mCurrentViewportMode;
    }
    return mode;
  }

  /**
   * Projection from WGS coordinate system to screen pixel coordinate system.
   *
   * @param geoPoint the WGS geo-point.
   * @param screenPoint the screen pixel-point.
   */
  public void toScreenPoint(GeoPoint geoPoint, ScreenPoint screenPoint)
  {
    screenPoint.x = (int)((geoPoint.mercX - getViewportLeft()) * mPixelViewportFactor);
    screenPoint.y = (int)(-1 * (geoPoint.mercY - getViewportBottom()) * mPixelViewportFactor);
  }

  /**
   *
   * @return
   */
  public GeoPoint getGridStart()
  {
    // To calculate the grid start for cell size at 100m,
    mPointGridStart.mercX = (int)(getViewportLeft() * RANGE_INV_100M) * RANGE_100M;
    mPointGridStart.mercY = (int)(getViewportBottom() * RANGE_INV_100M) * RANGE_100M;

    mPointGridStart.wgsLon = ((mPointGridStart.mercX % RANGE_200M) == 0) ? 1 : 0;
    mPointGridStart.wgsLat = ((mPointGridStart.mercY % RANGE_200M) == 0) ? 1 : 0;

    return mPointGridStart;
  }

  public double getGridCellPixelSize()
  {
    return (mPixelSize == 0) ? 0 : (RANGE_100M / mPixelSize);
  }

  //
  private void updatePixelSize()
  {
    mPixelViewportFactor = 0;
    mPixelSize = 0;

    double viewW = mViewport.getWidth();
    double viewH = mViewport.getHeight();

    if ((viewW == 0) || (viewH == 0)) {
      return;
    }

    mPixelViewportFactor = (mWidth / viewW);
    mPixelSize = (viewW / mWidth);
  }

  /** */
  private void updateCurrentViewportMode()
  {
    synchronized(mCurrentViewportMode)
    {
      setChanged();
      notifyObservers(mCurrentViewportMode);
    }
  }

  /** */
  private void setViewportMode(Integer viewportMode)
  {
    synchronized(mCurrentViewportMode)
    {
      mCurrentViewportMode = viewportMode;
    }
    setChanged();
    notifyObservers(viewportMode);
  }
}
